﻿using System.IO;
using System.Linq;
using System.Globalization;
using System.Collections.Generic;

namespace System.Xml.Linq
{
   public class XElement
   {
      XmlNode FInnerNode;
      static XmlDocument FCurrentDocument;
      static Dictionary<XmlNode, XElement> FXMap = new Dictionary<XmlNode, XElement>(100);

      XElement(XmlNode node)
      {
         FInnerNode = node;
      }

      XElement(XmlDocument doc)
      {
         FInnerNode = doc.DocumentElement;
      }

      public XElement(string name)
      {
         if (!OpenNewDocument(name))
         {
            FInnerNode = FCurrentDocument.CreateElement(name);
         }
      }

      public XElement(string name, object value)
      {
         if (!OpenNewDocument(name))
         {
            FInnerNode = FCurrentDocument.CreateElement(name);
         }
         if (value is IEnumerable<XElement>)
         {
            Add(value as IEnumerable<XElement>);
         }
         else if (value != null)
         {
            FInnerNode.InnerXml = value.ToString();
         }
      }

      public static void CloseCurrentDocument()
      {
         if (FCurrentDocument != null)
         {
            FCurrentDocument = null;
            FXMap.Clear();
         }
      }

      public override string ToString()
      {
         return FInnerNode == null ? "" : FInnerNode.OuterXml;
      }

      private bool OpenNewDocument(string rootElement)
      {
         if (FCurrentDocument == null)
         {
            FCurrentDocument = new XmlDocument();
            FCurrentDocument.LoadXml("<?xml version=\"1.0\" encoding=\"utf-8\"?>\r\n<" + rootElement + " />");
            FInnerNode = FCurrentDocument.DocumentElement;
            return true;
         }
         else
         {
            return false;
         }
      }

      private XmlNode FindChild(string name)
      {
         if (!FInnerNode.HasChildNodes) return null;

         foreach (XmlNode node in FInnerNode.ChildNodes)
         {
            if (node.Name == name) return node;
         }

         return null;
      }

      private XElement GetXElement(XmlNode node)
      {
         if (FXMap.ContainsKey(node))
         {
            return FXMap[node];
         }
         else
         {
            XElement x = new XElement(node);
            FXMap[node] = x;
            return x;
         }
      }

      private void IterateNodes(XmlNode node, List<XmlNode> list, string nameFilter)
      {
         if (string.IsNullOrEmpty(nameFilter) || node.Name == nameFilter) list.Add(node);

         foreach (XmlNode childNode in node.ChildNodes) IterateNodes(childNode, list, nameFilter);
      }


      internal XmlNode InnerNode
      {
         get
         {
            return FInnerNode;
         }
      }

      internal static XmlDocument CurrentDocument
      {
         get
         {
            return FCurrentDocument;
         }
      }


      public string Name
      {
         get
         {
            return FInnerNode.Name;
         }
      }

      public string Value
      {
         get
         {
            return FInnerNode.InnerText;
         }
         set
         {
            FInnerNode.InnerText = value;
         }
      }

      public XElement Parent
      {
         get
         {
            return FInnerNode.ParentNode != null ? GetXElement(FInnerNode.ParentNode) : null;
         }
      }

      public bool HasElements
      {
         get
         {
            return FInnerNode.HasChildNodes;
         }
      }

      public bool HasAttributes
      {
         get
         {
            return FInnerNode.Attributes.Count > 0;
         }
      }

      public void Save(string path)
      {
         if (FInnerNode == null || FInnerNode.OwnerDocument == null) throw new ArgumentException("Document you are attemting to save does not exist.");

         FInnerNode.OwnerDocument.Save(path);
      }

      public static XElement Load(string path)
      {
         XmlDocument doc = new XmlDocument();

         doc.Load(path);

         return new XElement(doc);
      }

      public static XElement Parse(string xml)
      {
         if (FCurrentDocument == null)
         {
            XmlDocument doc = new XmlDocument();

            doc.LoadXml(xml);

            return new XElement(doc);
         }
         else
         {
            XmlReaderSettings settings = new XmlReaderSettings();
            settings.IgnoreWhitespace = true;
            settings.CloseInput = true;
            settings.CheckCharacters = false;

            StringReader stringReader = new StringReader(xml);
            XmlReader xmlReader = XmlReader.Create(stringReader, settings);
            
            xmlReader.MoveToContent();
            XElement element = new XElement(FCurrentDocument.ReadNode(xmlReader));

            xmlReader.Close();
            stringReader.Dispose();

            return element;
         }
      }

      public void SetValue(object value)
      {
         if (value == null)
         {
            FInnerNode.InnerText = "";
         }
         else
         {
            FInnerNode.InnerText = value.ToString();
         }
      }

      public XElement Element(string name)
      {
         XmlNode node = FindChild(name);

         return node != null ? GetXElement(node) : null;
      }

      public XAttribute Attribute(string name)
      {
         if (FInnerNode.Attributes.Count == 0) return null;

         XmlAttribute attr = FInnerNode.Attributes[name];

         return attr != null ? new XAttribute(attr) : null;
      }

      public IEnumerable<XElement> Elements()
      {
         return FInnerNode.ChildNodes.Cast<XmlNode>().Select(node => GetXElement(node));
      }

      public IEnumerable<XElement> Descendants()
      {
         return Descendants("");
      }

      public IEnumerable<XAttribute> Attributes()
      {
         return FInnerNode.Attributes.Cast<XmlAttribute>().Select(attr => new XAttribute(attr));
      }

      public void Add(IEnumerable<XElement> elements)
      {
         foreach (XElement item in elements) Add(item);
      }

      public void Add(XElement element)
      {
         if (element.InnerNode.OwnerDocument == FInnerNode.OwnerDocument)
         {
            FInnerNode.AppendChild(element.InnerNode);
         }
         else
         {
            XmlNode node = FInnerNode.OwnerDocument.ImportNode(element.InnerNode, true);
            FInnerNode.AppendChild(node);
         }
      }

      public IEnumerable<XElement> Elements(string name)
      {
         return FInnerNode.ChildNodes.Cast<XmlNode>().Where(node => node.Name == name).Select(node => GetXElement(node));
      }

      public IEnumerable<XElement> Descendants(string name)
      {
         List<XmlNode> xContacts = new List<XmlNode>();

         IterateNodes(FInnerNode, xContacts, name);

         xContacts.Remove(FInnerNode);

         return xContacts.Select(node => GetXElement(node));
      }

      public void SetElementValue(string name, object value)
      {
         XmlNode node = FindChild(name);

         if (node == null)
         {
            node = FInnerNode.OwnerDocument.CreateElement(name);
            node.InnerXml = value.ToString();
            FInnerNode.AppendChild(node);
         }
         else
         {
            node.InnerXml = value.ToString();
         }
      }

      public void SetAttributeValue(string name, object value)
      {
         XmlAttribute attr = FInnerNode.Attributes[name];

         if (attr != null && value != null)
         {
            attr.Value = value.ToString();
         }
         else if (attr == null && value != null)
         {
            attr = FInnerNode.OwnerDocument.CreateAttribute(name);
            attr.Value = value.ToString();
            FInnerNode.Attributes.Append(attr);
         }
         else if (attr != null && value == null)
         {
            FInnerNode.Attributes.Remove(attr);
         }
      }

      public void Add(XElement element1, XElement element2, params XElement[] elements)
      {
         Add(element1);
         Add(element2);
         if (elements.Length > 0) Add(elements);
      }

      public void RemoveAttributes()
      {
         FInnerNode.Attributes.RemoveAll();
      }

      public void Remove()
      {
         if (FInnerNode.ParentNode != null)
         {
            FInnerNode.ParentNode.RemoveChild(FInnerNode);
         }
         else
         {
            FInnerNode.OwnerDocument.RemoveChild(FInnerNode);
         }
      }
   }

   public class XAttribute
   {
      XmlAttribute FXmlAttribute;

      XAttribute()
      {
      }
      internal XAttribute(XmlAttribute xmlAttribute)
      {
         FXmlAttribute = xmlAttribute;
      }

      public string Name
      {
         get
         {
            return FXmlAttribute.Name;
         }
      }
      public string Value
      {
         get
         {
            return FXmlAttribute.Value;
         }
         set
         {
            FXmlAttribute.Value = value;
         }
      }
      public override string ToString()
      {
         return FXmlAttribute.ToString();
      }

      [CLSCompliant(false)]
      public static explicit operator DateTime?(XAttribute attribute)
      {
         if (attribute == null)
         {
            return null;
         }
         return new DateTime?(DateTime.Parse(attribute.Value, CultureInfo.InvariantCulture, DateTimeStyles.RoundtripKind));
      }

      [CLSCompliant(false)]
      public static explicit operator bool(XAttribute attribute)
      {
         if (attribute == null)
         {
            throw new ArgumentNullException("attribute");
         }
         return XmlConvert.ToBoolean(attribute.Value.ToLower(CultureInfo.InvariantCulture));
      }

      [CLSCompliant(false)]
      public static explicit operator Guid(XAttribute attribute)
      {
         if (attribute == null)
         {
            throw new ArgumentNullException("attribute");
         }
         return XmlConvert.ToGuid(attribute.Value);
      }

      [CLSCompliant(false)]
      public static explicit operator bool?(XAttribute attribute)
      {
         if (attribute == null)
         {
            return null;
         }
         return new bool?(XmlConvert.ToBoolean(attribute.Value.ToLower(CultureInfo.InvariantCulture)));
      }

      [CLSCompliant(false)]
      public static explicit operator int(XAttribute attribute)
      {
         if (attribute == null)
         {
            throw new ArgumentNullException("attribute");
         }
         return XmlConvert.ToInt32(attribute.Value);
      }

      [CLSCompliant(false)]
      public static explicit operator Guid?(XAttribute attribute)
      {
         if (attribute == null)
         {
            return null;
         }
         return new Guid?(XmlConvert.ToGuid(attribute.Value));
      }

      [CLSCompliant(false)]
      public static explicit operator int?(XAttribute attribute)
      {
         if (attribute == null)
         {
            return null;
         }
         return new int?(XmlConvert.ToInt32(attribute.Value));
      }

      [CLSCompliant(false)]
      public static explicit operator TimeSpan?(XAttribute attribute)
      {
         if (attribute == null)
         {
            return null;
         }
         return new TimeSpan?(XmlConvert.ToTimeSpan(attribute.Value));
      }

      [CLSCompliant(false)]
      public static explicit operator DateTime(XAttribute attribute)
      {
         if (attribute == null)
         {
            throw new ArgumentNullException("attribute");
         }
         return DateTime.Parse(attribute.Value, CultureInfo.InvariantCulture, DateTimeStyles.RoundtripKind);
      }

      [CLSCompliant(false)]
      public static explicit operator DateTimeOffset?(XAttribute attribute)
      {
         if (attribute == null)
         {
            return null;
         }
         return new DateTimeOffset?(XmlConvert.ToDateTimeOffset(attribute.Value));
      }

      [CLSCompliant(false)]
      public static explicit operator long?(XAttribute attribute)
      {
         if (attribute == null)
         {
            return null;
         }
         return new long?(XmlConvert.ToInt64(attribute.Value));
      }

      [CLSCompliant(false)]
      public static explicit operator ulong?(XAttribute attribute)
      {
         if (attribute == null)
         {
            return null;
         }
         return new ulong?(XmlConvert.ToUInt64(attribute.Value));
      }

      [CLSCompliant(false)]
      public static explicit operator DateTimeOffset(XAttribute attribute)
      {
         if (attribute == null)
         {
            throw new ArgumentNullException("attribute");
         }
         return XmlConvert.ToDateTimeOffset(attribute.Value);
      }

      [CLSCompliant(false)]
      public static explicit operator float?(XAttribute attribute)
      {
         if (attribute == null)
         {
            return null;
         }
         return new float?(XmlConvert.ToSingle(attribute.Value));
      }

      [CLSCompliant(false)]
      public static explicit operator decimal(XAttribute attribute)
      {
         if (attribute == null)
         {
            throw new ArgumentNullException("attribute");
         }
         return XmlConvert.ToDecimal(attribute.Value);
      }

      [CLSCompliant(false)]
      public static explicit operator double?(XAttribute attribute)
      {
         if (attribute == null)
         {
            return null;
         }
         return new double?(XmlConvert.ToDouble(attribute.Value));
      }

      [CLSCompliant(false)]
      public static explicit operator double(XAttribute attribute)
      {
         if (attribute == null)
         {
            throw new ArgumentNullException("attribute");
         }
         return XmlConvert.ToDouble(attribute.Value);
      }

      [CLSCompliant(false)]
      public static explicit operator decimal?(XAttribute attribute)
      {
         if (attribute == null)
         {
            return null;
         }
         return new decimal?(XmlConvert.ToDecimal(attribute.Value));
      }

      [CLSCompliant(false)]
      public static explicit operator long(XAttribute attribute)
      {
         if (attribute == null)
         {
            throw new ArgumentNullException("attribute");
         }
         return XmlConvert.ToInt64(attribute.Value);
      }

      [CLSCompliant(false)]
      public static explicit operator uint?(XAttribute attribute)
      {
         if (attribute == null)
         {
            return null;
         }
         return new uint?(XmlConvert.ToUInt32(attribute.Value));
      }

      [CLSCompliant(false)]
      public static explicit operator float(XAttribute attribute)
      {
         if (attribute == null)
         {
            throw new ArgumentNullException("attribute");
         }
         return XmlConvert.ToSingle(attribute.Value);
      }

      [CLSCompliant(false)]
      public static explicit operator string(XAttribute attribute)
      {
         if (attribute == null)
         {
            return null;
         }
         return attribute.Value;
      }

      [CLSCompliant(false)]
      public static explicit operator TimeSpan(XAttribute attribute)
      {
         if (attribute == null)
         {
            throw new ArgumentNullException("attribute");
         }
         return XmlConvert.ToTimeSpan(attribute.Value);
      }

      [CLSCompliant(false)]
      public static explicit operator uint(XAttribute attribute)
      {
         if (attribute == null)
         {
            throw new ArgumentNullException("attribute");
         }
         return XmlConvert.ToUInt32(attribute.Value);
      }

      [CLSCompliant(false)]
      public static explicit operator ulong(XAttribute attribute)
      {
         if (attribute == null)
         {
            throw new ArgumentNullException("attribute");
         }
         return XmlConvert.ToUInt64(attribute.Value);
      }


   }
}
